29. DOM

29. DOM

1. DOM (Document Object Model)

  • DOM : DOM Tree + DOM API

    • 브라우저의 렌더링 엔진이 웹 문서를 파싱해서 메모리에 저장한 결과물

    • 즉, DOM이란 모든 element와 element의 attribute, text를 각각의 객체로 만들고 이 객체들을 부모-자식 관계를 나타낼 수 있는 트리 구조로 구성한 것

    • DOM은 자바스크립트를 통해 동적으로 변경할 수 있고 변경된 DOM은 브라우저 렌더링에 반영된다.

    • DOM API(Application Programming Interface) : 웹 문서의 동적 변경을 위해서 DOM에 접근할 수 있도록 제공하는 자바스크립트 객체의 프로퍼티와 메소드의 집합. DOM API를 통해서 DOM에 접근하고 변경해서 웹 페이지를 동적으로 변경할 수 있다.

    DOM tree | HTML 문서에 대한 모델 구성

    브라우저가 HTML 문서를 로드한 후 해당 문서에 대한 모델을 트리 구조로 메모리에 생성한 것

    DOM API ㅣ HTML 문서 내의 각 요소에 접근, 수정

    모델 내의 각 객체에 접근하고 수정할 수 있도록 DOM이 제공하는 프로퍼티와 메소드. DOM이 수정되면 브라우저를 통해 웹페이지의 내용이 변경된다.

  • jQuery

    • DOM 객체를 보다 직관적으로 조작
    • SPA에는 안 맞음
  • SPA(Single Page Application)

    • SPA Framework : Angular, React
    • html 문서를 하나를 계속 씀
    • JavaScript로 html 다시 그림

2. DOM tree

HTML 문서 내부에서 element의 중첩 관계는 트리에서 부모-자식 관계로 표현된다.

HTML Element 별로 다른 속성을 표현하기 위해서 브라우저에서는 DOM Parsing시 Element의 정보를 DOM 객체의 상속관계로 표현한다.

Proprties

DOM Element(node)는 HTML Element의 상태를 보관한다.

DOM tree는 4 종류의 노드로 구성된다.

Document Node

트리의 최상위 루트(root). 각 Element, Attribute, Text node에 접근하기 위해서는 트리의 루트인 Document Node를 시작으로 검색해야 한다.

Element Node

Element node는 HTML element를 표현한다. HTML element는 nesting(중첩)에 의해서 부모-자식 관계를 가지며 이를 통해서 정보를 구조화한다. 따라서 Element Node는 문서의 구조를 표현한다. Attribute, Text node에 접근하기 위해서는 우선 Element node에 접근해야 한다.

각 Element의 특성을 표현하기 위해서 객체의 상속을 이용한다. 이 때 모든 Element node는 HTMLElement 객체를 상속한다.

Attribute Node

Attribute Node는 HTML element의 attribute를 표현. Attribute Node는 자신을 가지는 Element의 자식이 아니라 형제(sibling)로 표현된다.

Text Node

Text Node는 HTML element의 텍스트를 표현한다. Text Node는 Element node의 자식이며 자신의 자식을 가질 수 없다. 즉 DOM tree의 단말 노드이다.

3. DOM Query

DOM을 통해 웹페이지를 조작하기 위해서는 우선 DOM 객체(element)에 접근할 수 있어야 한다.

DOM API를 이용해서 DOM Element에 접근할 수 있다. document 객체에 DOM API가 정의되어있다.

HTML element에 id 사용을 자제해야하는 이유

  1. 중복되면 안되지만 중복되어도 에러가 나지 않는다.
  2. HTML element의 id는 전역변수이다.

되도록 class를 사용하는 것이 좋다.

document.getElementById(id)

  • id attribute 값으로 Element Node 하나를 선택한다. id가 중복될 경우 첫번째 element만 리턴한다.

  • Return: HTMLElement를 상속받은 객체

  • 모든 브라우저에서 동작한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <!DOCTYPE html>
    <html>
    <head>
    <style>
    .red { color: red; }
    .blue { color: blue; }
    .yellow { color: yellow; }
    </style>
    </head>
    <body>
    <div>
    <h1>Cities</h1>
    <ul>
    <li id="one" class="red">Seoul</li>
    <li id="two" class="blue">London</li>
    <li id="three" class="red">Newyork</li>
    <li id="four">Tokyo</li>
    </ul>
    </div>
    <script type="text/javascript">
    const elem = document.getElementById('one');
    elem.className = 'yellow';

    console.log(elem); // <li id="one" class="blue">Seoul</li>
    console.log(elem.__proto__); // HTMLLIElement
    console.log(elem.__proto__.__proto__); // HTMLElement
    console.log(elem.__proto__.__proto__.__proto__); // Element
    console.log(elem.__proto__.__proto__.__proto__.__proto__); // Node
    </script>
    </body>
    </html>

Proto Chain

document.querySelector(cssSelector)

  • CSS Selector를 사용해서 Element Node를 한 개 선택한다. 여러 개가 선택되면 첫번째 element만 리턴한다.
  • Return: HTMLElement를 상속받은 객체
  • IE8 이상 브라우저에서 동작 Can I use document.querySelector ?
  • getElement계열보다 querySelector를 쓰자.
  • 조건에 부합하는 대상이 없을 경우 null 리턴

document.getElementByClassName(class)

  • class attribute 값으로 Element Node를 모두 선택. id가 중복될 경우 첫번째 element만 리턴

  • Return: (live)HTMLCollection

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <body>
    <div>
    <h1>Cities</h1>
    <ul>
    <li id="one" class="red">Seoul</li>
    <li id="two" class="red">London</li>
    <li id="three" class="red">Newyork</li>
    <li id="four">Tokyo</li>
    </ul>
    </div>
    <script type="text/javascript">
    const elems = document.getElementsByClassName('red');
    console.log(elems);
    for (let i = 0; i < elems.length; i++) {
    elems[i].className = 'yellow';
    }
    </script>
    </body>

    HTMLCollection이란, 배열이 아닌 유사배열 객체(array-like object)이다. HTMLCollection은 실시간으로 Node의 상태가 변경될 때마다 Collection에 반영하기 때문에 liveHTMLCollection이라고도 한다.

    Result Page

    위 코드의 실행 결과로 두번째와 네번째 Element의 색이 바뀌지 않은 것은 elems가 참조하는 HTMLCollection이 실시간으로 변경되어 누락되었기 때문이다.

    1. for 문을 역방향으로 돌리기
    2. while 문 사용
    3. HTMLCollection을 배열로 변경
    4. querySelectorAll 메소드를 사용해서 non-live NodeList를 리턴하게 함

    등으로 이런 현상을 방지할 수 있다.

  • IE9 이상의 브라우저에서 동작

document.getElementsByTagName(tagName)

1
2
3
4
5
6
7
 <script type="text/javascript">
const elems = document.getElementsByTagName('li');
console.log(elems); // HTMLCollection(4) [li#one.red, li#two.red, li#three.red, li#four, one: li#one.red, two: li#two.red, three: li#three.red, four: li#four]
for (var i = 0; i < elems.length; i++) {
elems[i].className = 'blue';
}
</script>

getElementsByTagName 메소드도 HTMLCollection을 리턴한다고 명세에 써있으나 Webkit에서는 NodeList를 리턴한다. 크롬에서는 NodeList를 리턴한다.

Result Page

document.querySelectorAll(selector)

  • CSS Selector를 사용하여 Element Node를 모두 선택.
  • Return: NodeList (non-live)
  • IE8 이상 브라우저에서 동작
  • 여러개 선택할 때는 getElements 대신 querySelectorAll을 쓰자.
  • 조건에 부합하는 대상이 없을 경우 빈 NodeList [] 리턴

element.className

class="red blue"에서 ‘red blue’를 의미. class를 한 번에 전부 수정하는 데에 좋다. 하나를 추가하고 싶을 때는 아래와 같이 쓴다.

1
2
const elem = document.querySelector('#one');
elem.className += 'yellow';

추가나 삭제에는 classList의 method를 사용하는 것이 편리하다.

1
2
3
const elem = document.querySelector('#one');
elem.classList.add('blue');
elem.classList.remove('blue');

DOM Traversal

parentNode

자신의 parent 노드를 탐색. HTMLElement를 상속받은 객체를 리턴하며 모든 브라우저에서 동작한다.

firstChild, lastChild

자신의 첫번째 child, 마지막 child 노드를 탐색. HTMLELement를 상속받은 객체를 리턴하며 IE9 이상의 브라우저에서 동작한다.

IE를 제외한 대부분의 브라우저에서는 element 사이의 공백과 줄바꿈을 Text Node로 취급하기 때문에 주의해야 한다.

hasChildNodes()

child 노트가 있는지 확인하고 Boolean을 리턴. 모든 브라우저에서 동작

childNodes

child 노드의 Collection을 리턴. NodeList(non-live)가 리턴됨. 모든 브라우저에서 동작

children

child 노드의 Collection을 리턴. HTMLCollection(live)가 리턴됨. IE9 이상 브라우저에서 동작. childNodes 대신 children을 쓰자

previousSibling, nextSibling

Text Node를 포함한 모든 형제 노드를 탐색. HTMLElement를 상속받은 객체가 리턴됨. 모든 브라우저에서 동작

previosElementSibling, nextElementSibling,

Element type의 형제 노드만을 탐색. HTMLElement를 상속받은 객체가 리턴됨. IE9 이상 브라우저에서 동작

4. DOM Manipulation

1. Text Node Access and Manipulation

  1. Text Node의 parent인 Element Node를 선택한다.
  2. firstChild 프로퍼티로 Text Node를 탐색한다.
  3. Text Node의 프로퍼티 nodeValue를 이용해서 텍스트에 접근하고 수정한다.

nodeValue

  • 노드의 값을 반환
  • Return: 텍스트 노드의 경우는 문자열, 요소 노드의 경우 null 반환
  • IE6 이상의 브라우저에서 동작

2. Attribute Node Access and Manipulation

Attribute Node 조작을 위해서 다음 프로퍼티 또는 메소드를 사용할 수 있다.

className

  • class attribute 값에 접근하고 할당할 수 있다. class attribute가 없는데 className을 할당하면 class atrribute를 생성하고 설정한다. className이 여러개일 경우 ' '(공백)으로 구분된 string이 반환되므로 String.split(‘ ‘)를 사용해서 변경한다.
  • 모든 브라우저에서 동작

classList

  • add, remove, item, toggle, contains, replace method 제공
  • 어떤 element의 class attribute를 추가, 삭제, 검사할 때 유용하다.
  • IE10 이상 브라우저에서 동작한다.

id

  • id attribute의 값에 접근하고 변경할 수 있다. className과 마찬가지로 없는데 할당시 생성하고 설정한다.
  • 모든 브라우저에서 동작

hasAttribute(attribute)

  • 지정한 어트리뷰트를 가지고 있는지 검사.
  • Return : Boolean
  • IE8 이상의 브라우저에서 동작.

getAttribute(attribute)

  • 어트리뷰트의 값을 취득.
  • Return : 문자열
  • 모든 브라우저에서 동작.

setAttribute(attribute, value)

  • 어트리뷰트와 어트리뷰트 값을 설정.
  • Return : undefined
  • 모든 브라우저에서 동작.

removeAttribute(attribute)

  • 지정한 어트리뷰트를 제거.
  • Return : undefined
  • 모든 브라우저에서 동작.

Difference between Attribute and Property

checkbox input element의 상태를 변경하는 방법 2가지 attribute이용, property 이용

  1. Attribute 이용

    element.setAttribute(‘chekced’, true);

    setting 전에는 변하지 않는 것. element의 초기값

  2. Property 이용

    element.checked = true;

    실시간으로 값이 변함. element의 현재값(최신 상태)

4.3. HTML Contents Manipulation

HTML contents를 조작하기 위해 아래와 같은 프로퍼티와 메소드를 사용할 수 있으나 Markup이 포함된 content를 추가하는 것은 XSS(Cross-Site Scripting Attacks)에 취약하므로 조심해야 한다.

textContent

  • Element의 text content에 접근하고 변경할 수 있다. 이 때 입력 문자열에 Markup이 포함되더라도 그냥 텍스트로 출력된다.
  • IE9 이상 브라우저에서 동작

innerText

  • textContent와 같이 element의 text content에만 접근할 수 있지만 비표준이며 CSS를 고려한다.
  • 사용하지 않는 것이 좋음

innerHTML

  • 해당 element 내부의 모든 html contents를 하나의 string으로 취득하고 변경할 수 있다.
  • Markup을 포함한다.
  • 그러나 Markup이 포함된 contents를 추가하는 것은 Cross-Site Scripting Attack에 취약하다.

4.4 DOM Manipulation Method

보안상 취약한 innerHTML 프로퍼티를 사용하지 않고 새로운 content를 추가할 수 있는 방법은 DOM을 직접 조작하는 것이다. 주로 하나의 element를 추가하는 경우 사용한다.

  1. createElement() method로 새로운 Element Node를 생성. Argument로 tag이름 전달.
  2. createTextNode() method로 새로운 Text Node를 생성. 1에서 만든 Element Node에 TextNode를 추가하지 않으면 content가 비어있는 element가 된다.
  3. appendChild() method로 생성된 element를 DOM에 추가한다. removeChild() method로는 DOM tree에서 node를 삭제할 수 있다.

createElement(tagName)

  • 노드의 값을 리턴
  • Return: 텍스트 노드의 경우는 문자열, 요소 노드의 경우 null 리턴
  • IE6 이상의 브라우저에서 동작

createTextNode(text)

  • 텍스트를 인자로 전달하여 텍스트 노드를 생성
  • Return: Text 객체
  • 모든 브라우저에서 동작

appendChild(Node)

  • argument로 전달한 노드를 마지막 자식 요소로 DOM 트리에 추가
  • Return: 추가한 노드
  • 모든 브라우저에서 동작

removeChild(Node)

  • argument로 전달한 노드를 DOM 트리에 제거
  • Return: 추가한 노드
  • 모든 브라우저에서 동작

4.5 insertAdjacentHTML()

insertAdjacentHTML(position, string)

  • 인자로 전달한 텍스트를 HTML로 파싱하고 그 결과로 생성된 노드를 DOM 트리의 지정된 위치에 삽입한다.
  • position : insert position
    • ‘beforebegin’
    • ‘afterbegin’
    • ‘beforeend’
    • ‘afterend’
  • string: 삽입할 element string
  • 모든 브라우저에서 동작

4.6. innerHTML vs. DOM manipulation vs. insertAdjacentHTML()

장단점 innerHTML DOM manipulation insertAdjacentHTML()
장점 DOM 조작 방식에 비해 빠르고 간편
간편하게 문자열로 정의한 여러 요소를 DOM에 추가할 수 있음
콘텐츠를 취득할 수 있음
특정 노드 한 개(노드, 텍스트, 데이터 등)를 DOM에 추가할 때 적합 간편하게 문자열로 정의된 여러 요소를 DOM에 추가할 수 있음
요소가 삽입되는 위치를 선정할 수 있음
단점 XSS공격에 취약점이 있기 때문에 사용자로 부터 입력받은 콘텐츠(untrusted data: 댓글, 사용자 이름 등)를 추가할 때 주의해야 함
해당 요소의 내용을 덮어 쓴다. 즉, HTML을 다시 파싱한다. 비효율적
innerHTML보다 느리고 더 많은 코드가 필요 XSS공격에 취약점이 있기 때문에 사용자로 부터 입력받은 콘텐츠(untrusted data: 댓글, 사용자 이름 등)를 추가할 때 주의해야 함

결론적으로 Text를 추가하거나 변경할 때는 textContent, 새로운 element를 추가하거나 삭제할 때는 DOM manipulation 방식(createElement() 등)을 사용한다.

5. style Property

style 프로퍼티를 사용하면 inline style 선언을 생성한다. 특정 element에 inline style을 지정할 때 사용한다.

1
2
3
4
5
6
7
const $four = document.getElementById('four');

// inline 스타일 선언을 생성
$four.style.color = 'blue';

// font-size와 같이 '-'으로 구분되는 프로퍼티는 카멜케이스로 변환해 사용한다.
$four.style.fontSize = '2em';

6. DOM load, “DOMContentLoaded” event

1
2
3
4
5
6
7
8
9
function getTodos() {
...
}
// DOM parsing 되고 resource도 로드됐을 때 발생하는 event
window.onload = getTodos;
// getTodos에 argument 넘길 수 있음
window.onload = function() {
getTodos;
};

DOMContentLoaded는 리소스 상관 없이 DOM parsing만 완료되었을 때 발생하는 event


Reference

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×